home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 November: Tool Chest / Dev.CD Nov 00 TC Disk 1.toast / Sample Code / Archive / Graphics / QD3D / WorldRayPickSample / Source / WRay_Pick.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-09-28  |  14.9 KB  |  653 lines  |  [TEXT/CWIE]

  1. /* 
  2.  *    WRay_Pick.c
  3.  *
  4.  *    QuickDraw 3D 1.6 Sample
  5.  *    Robert Dierkes
  6.  *
  7.  *     07/28/98    RDD        Created.
  8.  */
  9.  
  10. /*------------------*/
  11. /*    Include Files    */
  12. /*------------------*/
  13. #include "QD3D.h"
  14. #include "QD3DMath.h"
  15. #include "QD3DGroup.h"
  16. #include "QD3DPick.h"
  17. #include "QD3DTransform.h"
  18. #include "QD3DView.h"
  19.  
  20. #include "WRay_Error.h"
  21. #include "WRay_Document.h"
  22. #include "WRay_Memory.h"
  23. #include "WRay_Scene.h"
  24. #include "WRay_System.h"
  25. #include "WRay_Pick.h"
  26.  
  27. #include <math.h>
  28.  
  29.  
  30. /*------------------*/
  31. /*      Constants        */
  32. /*------------------*/
  33.  
  34. /*------------------*/
  35. /*      Macros        */
  36. /*------------------*/
  37. #define    kFirstHit                0
  38. #define    kHitDistance            (kCylRadius + 0.01f)
  39. #define    HAS_Object(validMask)    ((validMask & kQ3PickDetailMaskObject)    !=0)
  40. #define    HAS_Distance(validMask)    ((validMask & kQ3PickDetailMaskDistance)!=0)
  41.  
  42.  
  43. /*----------------------*/
  44. /*    Global Declarations    */
  45. /*----------------------*/
  46. static    TQ3PickObject        gPick = NULL;
  47. static    TQ3Ray3D            gWorldRay;
  48. static    TQ3Vector3D            gWorldDelta;
  49.  
  50. static    TQ3GroupPosition    gBallGroupPosn    = NULL;
  51. static    TQ3GeometryObject    gBallGeo = NULL;
  52. static    TQ3GeometryObject    gCurGeoHit    = NULL;
  53.  
  54.  
  55. /*----------------------*/
  56. /*    Local Prototypes    */
  57. /*----------------------*/
  58. static
  59. TQ3PickObject Pick_New(
  60.             TQ3Ray3D            *pWorldRay);
  61. static
  62. TQ3Status Pick_Document(
  63.             TDocumentPtr        pDoc,
  64.             TQ3PickObject        pickObj,
  65.             unsigned long         *pNumHits);
  66. static
  67. TQ3Status Pick_InitializeWorldRay(
  68.             TQ3PickObject        pickObj,
  69.             TQ3Ray3D            *pWorldRay,
  70.             TQ3Vector3D            *pWorldDelta);
  71. static
  72. TQ3Status Pick_ComputeReflectedWorldRay(
  73.             TQ3PickObject        pickObj,
  74.             TQ3Ray3D            *pWorldRay,
  75.             TQ3Vector3D            *pWorldDelta);
  76. static
  77. TQ3GeometryObject Pick_CreateMovingObject(
  78.             TDocumentPtr        pDoc,
  79.             TQ3Ray3D            *pRay,
  80.             TQ3GroupPosition    *pGroupPosn);
  81. static
  82. TQ3Status Pick_GetNearestObject(
  83.             TQ3PickObject        pickObj,
  84.             float                minDistance,
  85.             TQ3GeometryObject    *pGeoHit,
  86.             TQ3GeometryObject    *pPrevGeoHit);
  87. static
  88. TQ3Status Pick_HighlightObject(
  89.             TDocumentPtr        pDoc,
  90.             TQ3GeometryObject    geoObj,
  91.             TQ3Boolean            doHighlight);
  92.  
  93.  
  94. /*
  95.  *    Pick_Initialize
  96.  */
  97. TQ3Boolean Pick_Initialize(
  98.     void)
  99. {
  100.     TQ3Status    status = kQ3Failure;
  101.  
  102.     gPick            = NULL;
  103.     gBallGroupPosn    = NULL;
  104.     gBallGeo        = NULL;
  105.     gCurGeoHit        = NULL;
  106.  
  107.     Q3Point3D_Set(&gWorldRay.origin,     0.0, 0.0, 0.0);
  108.     Q3Vector3D_Set(&gWorldRay.direction, 0.0, 0.0, 1.0);
  109.  
  110.     gPick = Pick_New(&gWorldRay);
  111.  
  112.     return (gPick != NULL) ? kQ3True : kQ3False;
  113. }
  114.  
  115.  
  116. /*
  117.  *    Pick_Exit
  118.  */
  119. TQ3Boolean Pick_Exit(
  120.     void)
  121. {
  122.     Object_Dispose_NULL(&gPick);
  123.     Object_Dispose_NULL(&gBallGeo);
  124.     Object_Dispose_NULL(&gCurGeoHit);
  125.  
  126.     return kQ3True;
  127. }
  128.  
  129.  
  130. #pragma mark -
  131.  
  132. /*
  133.  *    Pick_New
  134.  */
  135. TQ3PickObject Pick_New(
  136.     TQ3Ray3D            *pWorldRay)
  137. {
  138.     TQ3WorldRayPickData    rayPickData;
  139.     TQ3PickObject        pickObject = NULL;
  140.  
  141.     /*
  142.      * Tolerance values are only used for Point, Line, Ellipse,
  143.      * NURB Curve, PolyLine, Mesh geometries and are measured
  144.      * in world space.
  145.      */
  146.     #define    kVertexTolerance    0.01
  147.     #define    kEdgeTolerance        0.005
  148.  
  149.     rayPickData.data.sort = kQ3PickSortNearToFar;
  150.  
  151.     rayPickData.data.mask = kQ3PickDetailMaskObject        |
  152.                             kQ3PickDetailMaskXYZ        |
  153.                             kQ3PickDetailMaskDistance    |
  154.                             kQ3PickDetailMaskNormal;
  155.     rayPickData.data.numHitsToReturn = 1;
  156.  
  157.     /* Make sure ray is normalized */
  158.     Q3Vector3D_Normalize(&pWorldRay->direction, &pWorldRay->direction);
  159.     rayPickData.ray = *pWorldRay;
  160.  
  161.     rayPickData.vertexTolerance    = kVertexTolerance;
  162.     rayPickData.edgeTolerance    = kEdgeTolerance;
  163.  
  164.     /* Create the new world ray pick object */
  165.     pickObject = Q3WorldRayPick_New(&rayPickData);
  166.     DEBUG_ASSERT(pickObject != NULL, Q3WorldRayPick_New);
  167.  
  168.     return pickObject;
  169. }
  170.  
  171.  
  172. /*
  173.  *    Pick_Document
  174.  */
  175. static
  176. TQ3Status Pick_Document(
  177.     TDocumentPtr        pDoc,
  178.     TQ3PickObject        pickObj,
  179.     unsigned long         *pNumHits)
  180. {
  181.     TQ3Status        status = kQ3Success;
  182.     TQ3ViewStatus    viewStatus = kQ3ViewStatusError;
  183.  
  184.     if (Q3View_StartPicking(pDoc->fView, pickObj) == kQ3Failure) {
  185.         ERROR_DEBUG_STR("Pick_Document: Q3View_StartPicking failed.");
  186.         return kQ3Failure;
  187.     }
  188.  
  189.     do {
  190.         Document_Submit_Objects(pDoc, pDoc->fView);
  191.  
  192.         viewStatus = Q3View_EndPicking(pDoc->fView);
  193.     }
  194.     while (viewStatus == kQ3ViewStatusRetraverse);
  195.  
  196.     DEBUG_ASSERT(viewStatus == kQ3ViewStatusDone, Pick_Document);
  197.  
  198.     status = Q3Pick_GetNumHits(pickObj, pNumHits);
  199.  
  200.     return status;
  201. }
  202.  
  203.  
  204. #pragma mark -
  205.  
  206. /*
  207.  *    Pick_IsAnimating
  208.  */
  209. TQ3Boolean Pick_IsAnimating(
  210.     void)
  211. {
  212.     return (gBallGeo != NULL) ? kQ3True : kQ3False;
  213. }
  214.  
  215.  
  216. /*
  217.  *    Pick_BeginAnimation
  218.  */
  219. TQ3Status Pick_BeginAnimation(
  220.     TDocumentPtr        pDoc)
  221. {
  222.     TQ3Status            status = kQ3Failure;
  223.  
  224.     if (Pick_IsAnimating() == kQ3True) {
  225.         /* Animation already taking place */
  226.         return status;
  227.     }
  228.  
  229.     /* Setup initial world ray origin and direction */
  230.     status = Pick_InitializeWorldRay(gPick, &gWorldRay, &gWorldDelta);
  231.     if (status == kQ3Failure) {
  232.         return status;
  233.     }
  234.  
  235.     /* Create ball */
  236.     gBallGeo = Pick_CreateMovingObject(pDoc, &gWorldRay, &gBallGroupPosn);
  237.     DEBUG_ASSERT(gBallGeo != NULL, Pick_CreateMovingObject);
  238.     if (gBallGeo == NULL) {
  239.         return kQ3Failure;
  240.     }
  241.  
  242.     return status;
  243. }
  244.  
  245.  
  246. /*
  247.  *    Pick_Animate
  248.  *
  249.  *    Move ball through the scene avoiding objects using
  250.  *    ray picking to check for geometries in its path.
  251.  */
  252. TQ3Status Pick_Animate(
  253.     TDocumentPtr        pDoc)
  254. {
  255.     TQ3Status            status    = kQ3Failure;
  256.     unsigned long        numHits;
  257.     TQ3GeometryObject    prevGeoHit    = NULL;
  258.  
  259.     if (Pick_IsAnimating() == kQ3False) {
  260.         /* If animation hasn't started then do nothing */
  261.         return kQ3Failure;
  262.     }
  263.  
  264.     /* Submit scene for picking */
  265.     status = Pick_Document(pDoc, gPick, &numHits);
  266.     DEBUG_ASSERT(status == kQ3Success, Pick_Document);
  267.  
  268.     if (numHits > 0) {
  269.         status = Pick_GetNearestObject(gPick, kHitDistance, &gCurGeoHit, &prevGeoHit);
  270.  
  271.         if (gCurGeoHit != prevGeoHit) {
  272.             if (prevGeoHit != NULL) {
  273.                 /* Unhighlight the ball & previous geometry */
  274.                 Pick_HighlightObject(pDoc, gBallGeo, kQ3False);
  275.                 Pick_HighlightObject(pDoc, prevGeoHit, kQ3False);
  276.                 Object_Dispose_NULL(&prevGeoHit);
  277.             }
  278.  
  279.             if (gCurGeoHit != NULL) {
  280.                 /* Highlight the ball & the new closest geometry*/
  281.                 Pick_HighlightObject(pDoc, gBallGeo, kQ3True);
  282.                 Pick_HighlightObject(pDoc, gCurGeoHit, kQ3True);
  283.  
  284.                 /* status = */ Pick_ComputeReflectedWorldRay(gPick, &gWorldRay, &gWorldDelta);
  285.  
  286.                 System_Sound();
  287.             }
  288.         }
  289.     }
  290.     else {
  291.         /* Nothing hit */
  292.         if (gCurGeoHit != NULL) {
  293.             /* Unhighlight the ball & current geometry */
  294.             Pick_HighlightObject(pDoc, gBallGeo, kQ3False);
  295.             Pick_HighlightObject(pDoc, gCurGeoHit, kQ3False);
  296.             Object_Dispose_NULL(&gCurGeoHit);
  297.         }
  298.     }
  299.  
  300.     Q3Pick_EmptyHitList(gPick);
  301.  
  302.     /* Advance ball by delta amount */
  303.     Q3Point3D_Vector3D_Add(&gWorldRay.origin, &gWorldDelta, &gWorldRay.origin);
  304.     Q3Ellipsoid_SetOrigin(gBallGeo, &gWorldRay.origin);
  305.  
  306.     /* Set ray again */
  307.     status = Q3WorldRayPick_SetRay(gPick, &gWorldRay);
  308.     DEBUG_ASSERT(status == kQ3Success, Q3WorldRayPick_SetRay);
  309.  
  310.     return status;
  311. }
  312.  
  313.  
  314. /*
  315.  *    Pick_EndAnimation
  316.  */
  317. TQ3Status Pick_EndAnimation(
  318.     TDocumentPtr        pDoc)
  319. {
  320.     if (Pick_IsAnimating() == kQ3False) {
  321.         /* Animation not taking place */
  322.         return kQ3Failure;
  323.     }
  324.  
  325.     /* Unhighlight anything that's still highlighted */
  326.     if (gCurGeoHit != NULL) {
  327.         /* Unhighlight the ball & current geometry */
  328.         Pick_HighlightObject(pDoc, gBallGeo, kQ3False);
  329.         Pick_HighlightObject(pDoc, gCurGeoHit, kQ3False);
  330.         Object_Dispose_NULL(&gCurGeoHit);
  331.     }
  332.     Object_Dispose_NULL(&gBallGeo);
  333.  
  334.     /* Remove temporary ball group from main group */
  335.     if (gBallGroupPosn != NULL) {
  336.         DEBUG_ASSERT(gCurGeoHit == NULL, Pick_EndAnimation);
  337.         gCurGeoHit = Q3Group_RemovePosition (pDoc->fModel, gBallGroupPosn);
  338.         Object_Dispose_NULL(&gCurGeoHit);
  339.         gBallGroupPosn = NULL;
  340.     }
  341.  
  342.     Document_Draw(pDoc);
  343.  
  344.     return kQ3Success;
  345. }
  346.  
  347.  
  348. #pragma mark -
  349.  
  350. /*
  351.  *    Pick_InitializeWorldRay
  352.  *
  353.  *    Create an world delta vector with a random direction with a
  354.  *    magnitude that controls the rate.  Normalize this vector to
  355.  *    make the actual world ray. The world ray is initially positioned
  356.  *    at the origin.
  357.  */
  358. static
  359. TQ3Status Pick_InitializeWorldRay(
  360.     TQ3PickObject        pickObj,
  361.     TQ3Ray3D            *pWorldRay,
  362.     TQ3Vector3D            *pWorldDelta)
  363. {
  364.     TQ3Status            status = kQ3Failure;
  365.  
  366.     Q3Point3D_Set(&pWorldRay->origin, 0.0, 0.0, 0.0);
  367.  
  368.     /* Generate a random vector in XY plane */
  369.     Q3Vector3D_Set(pWorldDelta, System_RandomFloat(), System_RandomFloat(), 0.0);
  370.     Q3Vector3D_Normalize(pWorldDelta, &pWorldRay->direction);
  371.  
  372.     /* Set world ray's origin and direction */
  373.     status = Q3WorldRayPick_SetRay(pickObj, pWorldRay);
  374.     DEBUG_ASSERT(status == kQ3Success, Q3WorldRayPick_SetRay);
  375.  
  376.     return status;
  377. }
  378.  
  379.  
  380. /*
  381.  *    Pick_ComputeReflectedWorldRay
  382.  *
  383.  *    Given:    pDelta, pRay->direction (a normalized pDelta), normal
  384.  *    Find:    new pDelta with a negative angle relative to the angle between delta and normal
  385.  *
  386.  *            ++
  387.  *           /!|\
  388.  *          / !| \
  389.  *         / N!|  \
  390.  *      D /   !|   \ D'
  391.  *       /    V|    \
  392.  *      /      |     \
  393.  *     /     D1|      \
  394.  *    v<-----  V ----->v
  395.  *        D2     D2'
  396.  *
  397.  *    !    N  = Normal
  398.  *    /    D  = Delta
  399.  *    |    D1 = Delta component
  400.  *    -    D2 = Delta component
  401.  *    \    D' = Reflection of D
  402.  *        c  = scalar for D projection onto N
  403.  *
  404.  *            D • N
  405.  *        c = -----    but since N • N = 1 then    c = D • N
  406.  *            N • N
  407.  *
  408.  *        D  = D1 + D2
  409.  *        D1 = cN
  410.  *
  411.  *        D  = cN + D2    therefore
  412.  *        D2 = D - cN
  413.  *
  414.  *        D2' = Negate(D2)
  415.  *        D' = D1 + D2'
  416.  */
  417. static
  418. TQ3Status Pick_ComputeReflectedWorldRay(
  419.     TQ3PickObject        pickObj,
  420.     TQ3Ray3D            *pWorldRay,
  421.     TQ3Vector3D            *pWorldDelta)
  422. {
  423.     TQ3Status            status;
  424.     TQ3Vector3D            normal,
  425.                         newDelta,
  426.                         delta1,
  427.                         delta2;
  428.     float                c;
  429.  
  430.     status = Q3Pick_GetPickDetailData (pickObj, kFirstHit, kQ3PickDetailMaskNormal, &normal);
  431.     DEBUG_ASSERT(status == kQ3Success, Q3Pick_GetPickDetailData);
  432.     if (status == kQ3Failure) {
  433.         return status;
  434.     }
  435.  
  436.     /* We negate pWorldDelta to reverse it's direction giving us the newDelta */
  437.     Q3Vector3D_Negate(pWorldDelta, &newDelta);
  438.  
  439.     /* Compute delta1 by projecting delta onto normal */
  440.     c = Q3Vector3D_Dot(&newDelta, &normal);
  441.     Q3Vector3D_Scale(&normal, c, &delta1);
  442.     Q3Vector3D_Subtract(&newDelta, &delta1, &delta2);
  443.  
  444.     /* Compute deltaPrime from delta1 and negated delta2. This is delta reflected through the normal */
  445.     Q3Vector3D_Negate(&delta2, &delta2);
  446.     Q3Vector3D_Add(&delta1, &delta2, pWorldDelta);
  447.  
  448.     /* Make sure pick ray is normalized */
  449.     Q3Vector3D_Normalize(pWorldDelta, &pWorldRay->direction);
  450.  
  451.     return status;
  452. }
  453.  
  454.  
  455. #pragma mark -
  456.  
  457. /*
  458.  *    Pick_CreateMovingObject
  459.  */
  460. static
  461. TQ3GeometryObject Pick_CreateMovingObject(
  462.             TDocumentPtr        pDoc,
  463.             TQ3Ray3D            *pRay,
  464.             TQ3GroupPosition    *pGroupPosn)
  465. {
  466.     TQ3EllipsoidData    data;
  467.     TQ3GeometryObject    geometry = NULL;
  468.     TQ3AttributeSet        attr     = NULL;
  469.     TQ3GroupObject        group     = NULL;
  470.  
  471.     #define    kObjectRadius        (kCylRadius * 0.95f)
  472.     #define    kDefaultObjectColor    0.8, 0.8, 0.0
  473.  
  474.     data.origin    = pRay->origin;
  475.  
  476.     data.orientation.x = 0.0;
  477.     data.orientation.y = kObjectRadius;
  478.     data.orientation.z = 0.0;
  479.  
  480.     data.majorRadius.x = 0.0;
  481.     data.majorRadius.y = 0.0;
  482.     data.majorRadius.z = kObjectRadius;
  483.  
  484.     data.minorRadius.x = kObjectRadius;
  485.     data.minorRadius.y = 0.0;
  486.     data.minorRadius.z = 0.0;
  487.  
  488.     data.uMin = data.vMin = 0.0f;
  489.     data.uMax = data.vMax = 1.0f;
  490.     data.caps = kQ3EndCapNone;
  491.  
  492.     data.interiorAttributeSet    = NULL;
  493.     data.ellipsoidAttributeSet    = NULL;
  494.  
  495.     attr = Q3AttributeSet_New ();
  496.     if (attr != NULL) {
  497.         TQ3ColorRGB    color = { kDefaultObjectColor };
  498.  
  499.         data.ellipsoidAttributeSet = attr;
  500.         Q3AttributeSet_Add(attr, kQ3AttributeTypeDiffuseColor, &color);
  501.  
  502.         geometry = Q3Ellipsoid_New(&data);
  503.         DEBUG_ASSERT(geometry != NULL, Q3Ellipsoid_New);
  504.         Object_Dispose_NULL(&attr);
  505.     }
  506.  
  507.     /* Put geometry in an unpickable group */
  508.     group = Q3OrderedDisplayGroup_New();
  509.     DEBUG_ASSERT(group != NULL, Q3OrderedDisplayGroup_New);
  510.  
  511.     if (group != NULL) {
  512.         TQ3DisplayGroupState    state;
  513.  
  514.         if (Q3DisplayGroup_GetState(group, &state) == kQ3Success) {
  515.             state &= ~kQ3DisplayGroupStateMaskIsPicked;
  516.             Q3DisplayGroup_SetState(group, state);
  517.         }
  518.  
  519.         /* Add geometry to group but don't decrement reference */
  520.         Q3Group_AddObject(group, geometry);
  521.  
  522.         /* Add group to main group */
  523.         *pGroupPosn = Q3Group_AddObject(pDoc->fModel, group);
  524.         Object_Dispose_NULL(&group);
  525.     }
  526.     else {
  527.         /* Error */
  528.         Object_Dispose_NULL(&geometry);
  529.     }
  530.  
  531.     return geometry;
  532. }
  533.  
  534.  
  535. /*
  536.  *    Pick_GetNearestObject
  537.  *
  538.  *    Find and return object hit if it's within minDistance.
  539.  *
  540.  *    In this sample code since we're picking with a single ray
  541.  *    and our moving object is several units wide portions of
  542.  *    this object may pass through other objects because we're
  543.  *    testing with a ray cast from the center of our moving object
  544.  *    rather than near it's outer extents.
  545.  */
  546. static
  547. TQ3Status Pick_GetNearestObject(
  548.     TQ3PickObject        pickObj,
  549.     float                minDistance,
  550.     TQ3GeometryObject    *pGeoHit,
  551.     TQ3GeometryObject    *pPrevGeoHit)
  552. {
  553.     TQ3Status            status    = kQ3Failure;
  554.     TQ3PickDetail        pickDetailValidMask;
  555.     float                distance;
  556.     TQ3GeometryObject    objectHit = NULL;
  557.  
  558.     #define    kDistanceTolerance    0.025        /* TODO This may need refinement */
  559.  
  560.     status = Q3Pick_GetPickDetailValidMask(pickObj, kFirstHit, &pickDetailValidMask);
  561.  
  562.     /* Get distance from ray to intersected geometry */
  563.     if (HAS_Distance(pickDetailValidMask)) {
  564.  
  565.         status = Q3Pick_GetPickDetailData (pickObj, kFirstHit, kQ3PickDetailMaskDistance, &distance);
  566.         DEBUG_ASSERT(status == kQ3Success, Q3Pick_GetPickDetailData);
  567.  
  568.         /* Is this geometry within "minDistance"? */
  569.         if ((distance <= (minDistance - kDistanceTolerance))    ||
  570.             (distance <= (minDistance + kDistanceTolerance)))
  571.         {
  572.             if (HAS_Object(pickDetailValidMask)) {
  573.                 /* Get the object */
  574.                 status = Q3Pick_GetPickDetailData (pickObj, kFirstHit, kQ3PickDetailMaskObject, &objectHit);
  575.  
  576.                 if (objectHit == *pGeoHit) {
  577.                     /* We've hit the same geometry */
  578.                     Object_Dispose_NULL(&objectHit);
  579.                 }
  580.                 else {
  581.                     /* Return new geometry hit */
  582.                     *pPrevGeoHit = *pGeoHit;    /* Need to unhighlight this if non-NULL */
  583.                     *pGeoHit     = objectHit;
  584.                 }
  585.             }
  586.         }
  587.         else {
  588.             *pPrevGeoHit = *pGeoHit;    /* Need to unhighlight this if non-NULL */
  589.             *pGeoHit     = NULL;
  590.         }
  591.     }
  592.  
  593.     return status;
  594. }
  595.  
  596.  
  597. /*
  598.  *    Pick_HighlightObject
  599.  */
  600. static
  601. TQ3Status Pick_HighlightObject(
  602.     TDocumentPtr        pDoc,
  603.     TQ3GeometryObject    geoObj,
  604.     TQ3Boolean            doHighlight)
  605. {
  606.     TQ3Status            status    = kQ3Failure;
  607.     TQ3AttributeSet        attr    = NULL;
  608.     TQ3Switch            isHighlighted = kQ3On;
  609.     TQ3Boolean            doesContain,
  610.                         doUpdate = kQ3False;
  611.  
  612.     DEBUG_ASSERT(pDoc != NULL, Pick_HighlightObject);
  613.     DEBUG_ASSERT(geoObj != NULL, Pick_HighlightObject);
  614.  
  615.     status = Q3Geometry_GetAttributeSet(geoObj, &attr);
  616.     DEBUG_ASSERT(status == kQ3Success, Q3Geometry_GetAttributeSet);
  617.     DEBUG_ASSERT(attr != NULL, Q3Geometry_GetAttributeSet__attr);
  618.  
  619.     if (status == kQ3Success) {
  620.         if (attr != NULL) {
  621.             doesContain = Q3AttributeSet_Contains(
  622.                             attr, kQ3AttributeTypeHighlightState);
  623.  
  624.             /* Add highlight if geometry doesn't contain one */
  625.             if (doHighlight == kQ3True) {
  626.                 if (doesContain == kQ3False) {
  627.                     Q3AttributeSet_Add(
  628.                                 attr, kQ3AttributeTypeHighlightState, &isHighlighted);
  629.                     doUpdate = kQ3True;
  630.                 }
  631.             }
  632.             else {
  633.                 /* Remove highlight if there is one */
  634.                 if (doesContain == kQ3True) {
  635.                     Q3AttributeSet_Clear(
  636.                                 attr, kQ3AttributeTypeHighlightState);
  637.                     doUpdate = kQ3True;
  638.                 }
  639.             }
  640.  
  641.             if (doUpdate == kQ3True) {
  642.                 status = Q3Geometry_SetAttributeSet(geoObj, attr);
  643.                 DEBUG_ASSERT(status == kQ3Success, Q3Geometry_SetAttributeSet);
  644.  
  645.                 Document_Draw(pDoc);
  646.             }
  647.         }
  648.     }
  649.     Object_Dispose_NULL(&attr);
  650.  
  651.     return status;
  652. }
  653.